package com.example.demo.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.aliyun.opensearch.DocumentClient;
import com.aliyun.opensearch.OpenSearchClient;
import com.aliyun.opensearch.SearcherClient;
import com.aliyun.opensearch.sdk.dependencies.com.google.common.collect.Lists;
import com.aliyun.opensearch.sdk.dependencies.com.google.common.collect.Maps;
import com.aliyun.opensearch.sdk.dependencies.org.json.JSONObject;
import com.aliyun.opensearch.sdk.generated.OpenSearch;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchClientException;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchException;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchResult;
import com.aliyun.opensearch.sdk.generated.document.Command;
import com.aliyun.opensearch.sdk.generated.document.DocumentConstants;
import com.aliyun.opensearch.sdk.generated.search.*;
import com.aliyun.opensearch.sdk.generated.search.general.SearchResult;
import com.aliyun.opensearch.search.SearchParamsBuilder;
import com.example.demo.enumeration.Campus;
import com.example.demo.enumeration.ItemCategory;
import com.example.demo.enumeration.ItemStatus;
import com.example.demo.enumeration.SortOrder;
import com.example.demo.pojo.dto.OpenSearchAddItemDTO;
import com.example.demo.pojo.dto.OpenSearchSelectItemDTO;
import com.example.demo.pojo.dto.UpdateItemDTO;
import com.example.demo.pojo.properties.AliyunOpenSearchProperties;

import java.math.BigDecimal;
import java.util.*;

public class AliyunOpenSearchUtil {

    // 创建ServiceClient对象
    private static OpenSearchClient createServiceClient(AliyunOpenSearchProperties aliyunOpenSearchProperties) {
        String accessKeyId = aliyunOpenSearchProperties.getAccessKeyId();
        String accessKeySecret = aliyunOpenSearchProperties.getAccessKeySecret();
        String host = aliyunOpenSearchProperties.getHost();

        //创建并构造OpenSearch对象
        OpenSearch openSearch = new OpenSearch(accessKeyId, accessKeySecret, host);
        //创建OpenSearchClient对象，并以OpenSearch对象作为构造参数
        OpenSearchClient serviceClient = new OpenSearchClient(openSearch);

        return serviceClient;
    }

    // 创建参数对象
    private static SearchParams createSearchParams(AliyunOpenSearchProperties aliyunOpenSearchProperties, int page) {
        String appName = aliyunOpenSearchProperties.getAppName();

        //定义Config对象，用于设定config子句参数，分页或数据返回格式，指定应用名等等
        Config config = new Config(Lists.newArrayList(appName));
        config.setStart((page - 1) * 30);
        config.setHits(30);
        //设置返回格式为FULLJSON，目前支持返回 XML，JSON，FULLJSON 等格式
        config.setSearchFormat(SearchFormat.FULLJSON);

        // 设置搜索结果返回应用中哪些字段
        config.setFetchFields(
                Lists.newArrayList(
                        "id"
                )
        );

        SearchParams searchParams = new SearchParams(config);

        // 设置biz参数
        Map hashMap = new HashMap();
        hashMap.put("biz", "type:web");
        searchParams.setCustomParam(hashMap);

        // 设置粗精排表达式，此处设置为默认
        Rank rank = new Rank();
        rank.setFirstRankName("item");
        rank.setSecondRankName("item");
        rank.setReRankSize(300); //设置参与精排文档个数
        //添加Aggregate对象参数
        searchParams.setRank(rank);

        return searchParams;
    }

    // 推送文档
    private static boolean pushDoc(AliyunOpenSearchProperties aliyunOpenSearchProperties, JSONObject json) throws OpenSearchClientException, OpenSearchException {
        // 创建ServiceClient对象
        OpenSearchClient serviceClient = createServiceClient(aliyunOpenSearchProperties);

        //定义DocumentClient对象添加json格式doc数据批量提交
        DocumentClient documentClient = new DocumentClient(serviceClient);

        com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray docsJsonArr = new com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray();
        docsJsonArr.put(json);
        String docsJson = docsJsonArr.toString();

        //执行推送操作
        OpenSearchResult osr = documentClient.push(docsJson, aliyunOpenSearchProperties.getAppName(), "item");

        //判断数据是否推送成功，主要通过判断2处，第一处判断用户方推送是否成功，第二处是应用控制台中有无报错日志
        //用户方推送成功后，也有可能在应用端执行失败，此错误会直接在应用控制台错误日志中生成，比如字段内容转换失败
        if (osr.getResult().equalsIgnoreCase("true")) {
            System.out.println(
                    "用户方推送无报错！\n以下为getTraceInfo推送请求Id:" +
                            osr.getTraceInfo().getRequestId()
            );

            return true;
        } else {
            System.out.println("用户方推送报错！" + osr.getTraceInfo());

            return false;
        }
    }


    /**
     * 搜索商品id列表
     * @param aliyunOpenSearchProperties 阿里云开放搜索服务的各种配置信息类
     * @param page 当前页码
     * @param keyword 搜索关键词
     * @param category 商品种类
     * @param campus 商品校区
     * @param minPrice 商品最低价格
     * @param maxPrice 商品最高价格
     * @param sortByPrice 价格排序方式
     * @return 带有所需数据的DTO
     */
    public static OpenSearchSelectItemDTO searchItem(AliyunOpenSearchProperties aliyunOpenSearchProperties, int page, String keyword, ItemCategory category, Campus campus, BigDecimal minPrice, BigDecimal maxPrice, SortOrder sortByPrice) throws OpenSearchClientException, OpenSearchException {
        // 创建ServiceClient对象
        OpenSearchClient serviceClient = createServiceClient(aliyunOpenSearchProperties);

        //创建SearcherClient对象，并以OpenSearchClient对象作为构造参数
        SearcherClient searcherClient = new SearcherClient(serviceClient);

        // 创建参数对象
        SearchParams searchParams = createSearchParams(aliyunOpenSearchProperties, page);

        if (keyword != null) {
            // 设置查询子句，若需多个索引组合查询，需要setQuery处合并（如下示例，可以用OR拼接），否则若设置多个setQuery后面的会替换前面查询
            String string = "name:'" + keyword + "' OR description:'" + keyword + "'";
            searchParams.setQuery(string);

            //添加raw_query参数(参数值需要和query查询词保持一致)
            searchParams.setRawQuery(keyword);
        }

        // 设置sort条件
        if (SortOrder.desc.equals(sortByPrice)) {
            Sort sorter = new Sort();
            sorter.addToSortFields(new SortField("price", Order.DECREASE));

            searchParams.setSort(sorter);
        } else if (SortOrder.asc.equals(sortByPrice)) {
            Sort sorter = new Sort();
            sorter.addToSortFields(new SortField("price", Order.INCREASE));

            searchParams.setSort(sorter);
        }

        // SearchParams的工具类，提供了更为便捷的操作
        SearchParamsBuilder paramsBuilder = SearchParamsBuilder.create(
                searchParams
        );

        // 设置查询过滤条件s
        paramsBuilder.addFilter("status=\"available\"", "AND");


        if (category != null) {
            paramsBuilder.addFilter("category=" + "\"" + category + "\"", "AND");
        }

        if (campus != null) {
            paramsBuilder.addFilter("campuses=" + "\"" + campus + "\"", "AND");
        }

        if (minPrice != null) {
            paramsBuilder.addFilter("price>" + minPrice, "AND");
        }

        if (maxPrice != null) {
            paramsBuilder.addFilter("price<" + maxPrice, "AND");
        }

        // 执行返回查询结果。用户需按code和message，进行异常情况判断。code对应的错误信息查看——错误码文档。
        SearchResult searchResult = searcherClient.execute(paramsBuilder);
        String result = searchResult.getResult();
        JSONObject obj = new JSONObject(result);

        OpenSearchSelectItemDTO openSearchSelectItemDTO = new OpenSearchSelectItemDTO();

        String status = obj.get("status").toString();
        if (!status.equals("OK")) {
            Object errors = obj.get("errors");

            openSearchSelectItemDTO.setErrors(errors.toString());
        }

        JSONObject map = (JSONObject)obj.get("result");

        long total = map.getLong("total");

        openSearchSelectItemDTO.setTotal(total);

        JSONArray items = JSON.parseArray(map.get("items").toString());

        List<Long> idList = new ArrayList<>();

        for (int i = 0; i < items.size(); i++) {
            com.alibaba.fastjson.JSONObject item = items.getJSONObject(i);
            com.alibaba.fastjson.JSONObject fields = item.getJSONObject("fields");
            Long id = fields.getLong("id");

            idList.add(id);
        }

        openSearchSelectItemDTO.setList(idList);

        return openSearchSelectItemDTO;
    }

    /**
     * 在阿里云开放搜索服务添加商品数据
     * @param aliyunOpenSearchProperties 阿里云开放搜索服务的各种配置信息类
     * @param openSearchAddItemDTO 带有需要添加的商品各种信息的DTO
     * @return 操作是否成功
     */
    public static boolean addItem(AliyunOpenSearchProperties aliyunOpenSearchProperties, OpenSearchAddItemDTO openSearchAddItemDTO) throws OpenSearchClientException, OpenSearchException {
        Map<String, Object> doc = Maps.newLinkedHashMap();
        doc.put("id", openSearchAddItemDTO.getId());
        doc.put("name", openSearchAddItemDTO.getName());
        doc.put("description", openSearchAddItemDTO.getDescription());
        doc.put("price", openSearchAddItemDTO.getPrice());
        doc.put("category", openSearchAddItemDTO.getCategory().toString());
        doc.put("user_id", openSearchAddItemDTO.getUserId());
        doc.put("status", ItemStatus.pending.toString());

        List<Campus> campuses = openSearchAddItemDTO.getCampuses();
        String[] campusNames = new String[campuses.size()];

        for (int i = 0; i < campuses.size(); i++) {
            campusNames[i] = campuses.get(i).name();
        }

        doc.put("campuses", campusNames);

        doc.put("create_time", Calendar.getInstance().getTimeInMillis());
        doc.put("update_time", Calendar.getInstance().getTimeInMillis());

        JSONObject json = new JSONObject();
        json.put(DocumentConstants.DOC_KEY_CMD, Command.ADD.toString());
        json.put(DocumentConstants.DOC_KEY_FIELDS, doc);

        boolean result = pushDoc(aliyunOpenSearchProperties, json);

        return result;
    }

    /**
     * 在阿里云开放搜索服务更新商品基本信息
     * @param aliyunOpenSearchProperties 阿里云开放搜索服务的各种配置信息类
     * @param updateItemDTO 带有更新商品数据的DTO
     * @return 操作是否成功
     */
    public static boolean updateItemInfo(AliyunOpenSearchProperties aliyunOpenSearchProperties, UpdateItemDTO updateItemDTO) throws OpenSearchClientException, OpenSearchException {
        Map<String, Object> doc = Maps.newLinkedHashMap();
        doc.put("id", updateItemDTO.getId());
        doc.put("name", updateItemDTO.getName());
        doc.put("description", updateItemDTO.getDescription());
        doc.put("category", updateItemDTO.getCategory().toString());
        doc.put("update_time", Calendar.getInstance().getTimeInMillis());

        List<Campus> campuses = updateItemDTO.getCampuses();
        String[] campusNames = new String[campuses.size()];

        for (int i = 0; i < campuses.size(); i++) {
            campusNames[i] = campuses.get(i).name();
        }

        doc.put("campuses", campusNames);

        JSONObject json = new JSONObject();
        json.put(DocumentConstants.DOC_KEY_CMD, Command.UPDATE.toString());
        json.put(DocumentConstants.DOC_KEY_FIELDS, doc);

        boolean result = pushDoc(aliyunOpenSearchProperties, json);

        return result;
    }

    /**
     * 在阿里云开放搜索服务更新商品价格
     * @param aliyunOpenSearchProperties 阿里云开放搜索服务的各种配置信息类
     * @param id 需要更新的商品id
     * @param price 商品新价格
     * @return 操作是否成功
     */
    public static boolean updateItemPrice(AliyunOpenSearchProperties aliyunOpenSearchProperties, Long id, BigDecimal price) throws OpenSearchClientException, OpenSearchException {
        Map<String, Object> doc = Maps.newLinkedHashMap();
        doc.put("id", id);
        doc.put("price", price);
        doc.put("update_time", Calendar.getInstance().getTimeInMillis());

        JSONObject json = new JSONObject();
        json.put(DocumentConstants.DOC_KEY_CMD, Command.UPDATE.toString());
        json.put(DocumentConstants.DOC_KEY_FIELDS, doc);

        boolean result = pushDoc(aliyunOpenSearchProperties, json);

        return result;
    }

    /**
     * 在阿里云开放搜索服务更新商品状态
     * @param aliyunOpenSearchProperties 阿里云开放搜索服务的各种配置信息类
     * @param id 需要更新的商品id
     * @param status 商品新状态
     * @return 操作是否成功
     */
    public static boolean updateItemStatus(AliyunOpenSearchProperties aliyunOpenSearchProperties, Long id, ItemStatus status) throws OpenSearchClientException, OpenSearchException {
        Map<String, Object> doc = Maps.newLinkedHashMap();
        doc.put("id", id);
        doc.put("status", status.toString());
        doc.put("update_time", Calendar.getInstance().getTimeInMillis());

        JSONObject json = new JSONObject();
        json.put(DocumentConstants.DOC_KEY_CMD, Command.UPDATE.toString());
        json.put(DocumentConstants.DOC_KEY_FIELDS, doc);

        boolean result = pushDoc(aliyunOpenSearchProperties, json);

        return result;
    }

    /**
     * 在阿里云开放搜索服务删除商品数据
     * @param aliyunOpenSearchProperties 阿里云开放搜索服务的各种配置信息类
     * @param id 需要删除的商品id
     * @return 操作是否成功
     */
    public static boolean deleteItem(AliyunOpenSearchProperties aliyunOpenSearchProperties, Long id) throws OpenSearchClientException, OpenSearchException {
        //删除文档只需要设置需删除文档主键值即可
        Map<String, Object> doc = Maps.newLinkedHashMap();
        doc.put("id", id);

        //此处设置删除文档处理
        JSONObject json = new JSONObject();
        json.put(DocumentConstants.DOC_KEY_CMD, Command.DELETE.toString());
        json.put(DocumentConstants.DOC_KEY_FIELDS, doc);

        boolean result = pushDoc(aliyunOpenSearchProperties, json);

        return result;
    }
}
